#include "dxgirenderer.h"

#include "d3d11renderer.h"
#include "d3d10renderer.h"

#include "dxcommon.h"
#include "renderer.h"

DXGIRenderer* DXGIRenderer::instance = NULL;

typedef HRESULT (__stdcall *Present_Type)(IDXGISwapChain*, UINT, UINT);

typedef HRESULT (__stdcall *D3D10CreateDeviceAndSwapChain_Type)(
  IDXGIAdapter* pAdapter, D3D_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags,
  UINT SDKVersion, DXGI_SWAP_CHAIN_DESC* pSwapChainDesc, IDXGISwapChain** ppSwapChain,
  ID3D10Device** ppDevice);
typedef HRESULT (__stdcall *D3D11CreateDeviceAndSwapChain_Type)(
  IDXGIAdapter* pAdapter, D3D_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags,
  const D3D_FEATURE_LEVEL* pFeatureLevels, UINT FeatureLevels, UINT SDKVersion,
  const DXGI_SWAP_CHAIN_DESC* pSwapChainDesc, IDXGISwapChain** ppSwapChain,
  ID3D11Device** ppDevice, D3D_FEATURE_LEVEL* pFeatureLevel, ID3D11DeviceContext** ppImmediateContext);

DXGIRenderer::DXGIRenderer(HINSTANCE hInstance, HMODULE hD3D10, HMODULE hD3D11, OverlayState const* pState)
  : state(pState)
  , d3d10Renderer(NULL)
  , d3d11Renderer(NULL)
{
  instance = this;

  void** pVTable = NULL;

  HWND hWnd = CreateDummyWindow(hInstance);
  if (hWnd == NULL) return;

	DXGI_SWAP_CHAIN_DESC scd;
  memset(&scd, 0, sizeof scd);
	scd.BufferCount = 1;
	scd.BufferDesc.Width = 1;
	scd.BufferDesc.Height = 1;
	scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	scd.OutputWindow = hWnd;
	scd.SampleDesc.Count = 1;
	scd.SampleDesc.Quality = 0;
	scd.Windowed = TRUE;

  if (hD3D11)
  {
    D3D11CreateDeviceAndSwapChain_Type d3d11Create = (D3D11CreateDeviceAndSwapChain_Type)
      GetProcAddress(hD3D11, "D3D11CreateDeviceAndSwapChain");
    IDXGISwapChain* pSwapChain = NULL;
    ID3D11Device* pDevice = NULL;
    if (d3d11Create && SUCCEEDED(d3d11Create(NULL, D3D_DRIVER_TYPE_REFERENCE, NULL, 0, NULL, 0, D3D11_SDK_VERSION,
        &scd, &pSwapChain, &pDevice, NULL, NULL)))
    {
      pVTable = *(void***) pSwapChain;
      pSwapChain->Release();
      pDevice->Release();
    }
  }
  if (pVTable == NULL && hD3D10)
  {
    D3D10CreateDeviceAndSwapChain_Type d3d10Create = (D3D10CreateDeviceAndSwapChain_Type)
      GetProcAddress(hD3D10, "D3D10CreateDeviceAndSwapChain");
    IDXGISwapChain* pSwapChain = NULL;
    ID3D10Device* pDevice = NULL;
    if (d3d10Create && SUCCEEDED(d3d10Create(NULL, D3D_DRIVER_TYPE_REFERENCE, NULL, 0, D3D10_SDK_VERSION,
        &scd, &pSwapChain, &pDevice)))
    {
      pVTable = *(void***) pSwapChain;
      pSwapChain->Release();
      pDevice->Release();
    }
  }
  DestroyWindow(hWnd);

  if (pVTable)
    hPresent.init(pVTable[8], myPresent);
}
DXGIRenderer::~DXGIRenderer()
{
  delete d3d10Renderer;
  delete d3d11Renderer;
  instance = NULL;
}

void DXGIRenderer::onDestroy(HWND hWnd)
{
  if (d3d10Renderer) d3d10Renderer->onDestroy(hWnd);
  if (d3d11Renderer) d3d11Renderer->onDestroy(hWnd);
}

HRESULT __stdcall DXGIRenderer::myPresent(IDXGISwapChain* pSwapChain, UINT SyncInterval, UINT Flags)
{
  if (instance == NULL)
    return DXGI_ERROR_INVALID_CALL;

  ID3D11Device* pD3D11Device = NULL;
  if (SUCCEEDED(pSwapChain->GetDevice(__uuidof(pD3D11Device), (void**) &pD3D11Device)))
  {
    ID3D11DeviceContext* pContext;
    ID3D11RenderTargetView* pRenderTarget;
    ID3D11DepthStencilView* pDepthStencil;
    pD3D11Device->GetImmediateContext(&pContext);
    pContext->OMGetRenderTargets(1, &pRenderTarget, &pDepthStencil);
    if (pRenderTarget)
    {
      if (!instance->d3d11Renderer)
        instance->d3d11Renderer = new D3D11Renderer(instance->state);
      instance->d3d11Renderer->render(pSwapChain, pD3D11Device);
    }
    COMRELEASE(pRenderTarget);
    COMRELEASE(pDepthStencil);
    COMRELEASE(pContext);
    COMRELEASE(pD3D11Device);
  }
  ID3D10Device* pD3D10Device = NULL;
  if (SUCCEEDED(pSwapChain->GetDevice(__uuidof(pD3D10Device), (void**) &pD3D10Device)))
  {
    ID3D10RenderTargetView* pRenderTarget;
    ID3D10DepthStencilView* pDepthStencil;
    pD3D10Device->OMGetRenderTargets(1, &pRenderTarget, &pDepthStencil);
    if (pRenderTarget)
    {
      if (!instance->d3d10Renderer)
        instance->d3d10Renderer = new D3D10Renderer(instance->state);
      instance->d3d10Renderer->render(pSwapChain, pD3D10Device);
    }
    COMRELEASE(pRenderTarget);
    COMRELEASE(pDepthStencil);
    COMRELEASE(pD3D10Device);
  }

  instance->hPresent.unhook();
  HRESULT res = pSwapChain->Present(SyncInterval, Flags);
  instance->hPresent.hook();
  return res;
}
